/*
 *  linux/kernel/serial.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *	serial.c
 *
 * This module implements the rs232 io functions
 *	void rs_write(struct tty_struct * queue);
 *	void rs_init(void);
 * and all interrupts pertaining to serial IO.
 */

#include <linux/tty.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/io.h>

#define WAKEUP_CHARS (TTY_BUF_SIZE/4)

extern void rs1_interrupt(void);
extern void rs2_interrupt(void);

// 初始化串行端口
// 参数port为：串口的端口基地址
static void init(int port)
{
	// 设置波特率因子
	/*
	设置通信传输波特率就是设置两个除数锁存寄存器 LSB 和 MSB 的值，即16位的波特率因子。
	若要访问这两个除数锁存寄存器，我们必须首先设置 线路控制寄存器 LCR 的位7 DLAB=1，即向端口 port+3(0x3fb）写入 0x80。
	然后对端口 port (0x3f8）和 port+1 (0x3f9）执行输出操作即可把波特率因子分别写入LSB 和 MSB中。
	波特率设置好后，我们最好还需要复位线路控制寄存器的 DLAB 位。
	*/
	outb_p(0x80,port+3);	/* set DLAB of line control reg */
	outb_p(0x30,port);	/* LS of divisor (48 -> 2400 bps 发送波特率因子低字节 */
	outb_p(0x00,port+1);	/* MS of divisor 发送波特率因子高字节*/
	// 设置通信传输格式
	/*
	串行通信传输格式由线路控制寄存器 LCR 中的各位来定义。其中每位的含义见《表10-8 UARI 内部寄存器对应端口及用途》所示。
	如果我们需要把传输格式设置成无奇偶校验位、8位数据位和 1 位停止位，那么就需要向 LCR 输出值 0x03。
	LCR 最低2 位表示数据位长度，当为11时表示数据长度是8位。
	复位LCR 的位7 DLAB=0
	*/
	outb_p(0x03,port+3);	/* reset DLAB */
	// 设置 MODEM 控制寄存器，实现对Modem的控制
	/*
	若要把 UART 设置成中断方式，并且使 DTR 和 RTS 有效，那么我们就需要向 MODEM 控制寄存器写入 0x0b，即二进制数 01011。
	*/
	outb_p(0x0b,port+4);	/* set DTR,RTS, OUT_2 */
	// 初始化中断允许寄存器
	/*
	除了写（写保持空）以外，允许所有中断源中断。
	位3=1 modem 状态中断允许；
	位2=1 接收器线路状态中断允许；
	位1=0 发送保持寄存器空中断禁止；
	位0=1 已接收到数据中断允许。
	*/
	outb_p(0x0d,port+1);	/* enable all intrs but writes */
	(void)inb(port);	/* read data port to reset things (?) */
}

// 串口初始化
void rs_init(void)
{
	// 设置串行口1的中断门向量
	set_intr_gate(0x24,rs1_interrupt);
	// 设置串行口2的中断门向量
	set_intr_gate(0x23,rs2_interrupt);
	/*
	当PC机上电启动时，系统 RESET 信号通过 NS8250 的 MR 引脚使得 UART 内部寄存器和控制逻辑复位。
	此后若要使用 UART 就需要对其进行初始化编程操作，以设置 UART 的工作波特率、数据位数以及工作方式等。
	*/
	// 对串行端口1进行初始化
	init(tty_table[1].read_q.data);
	// 对串行端口2进行初始化
	init(tty_table[2].read_q.data);
	// 允许主 8259A响应 IRQ3、 IRQ4 中断请求
	outb(inb_p(0x21)&0xE7,0x21);
}

/*
 * This routine gets called when tty_write has put something into
 * the write_queue. It must check wheter the queue is empty, and
 * set the interrupt register accordingly
 *
 *	void _rs_write(struct tty_struct * tty);
 */
/*
该函数实际上只是开启 发送保持寄存器 己空中断标志。此后，当发送保持寄存器空时，UART 就会产生中断请求。
中断发生后，在该串行中断处理过程中，程序会取出串行终端写队列尾指针处的字符，并输出到发送保持寄存器中。
一旦 UART 把该字符发送了出去，发送保持寄存器又会变空而引发新一次的中断请求。

于是只要写队列中还有字符，系统就会重复这个处理过程，把字符一个一个地发送出去。
当写队列中所有字符都发送了出去，写队列变空了，中断处理程序就会把中断允许奇存器中的发送保持寄存器中断允许标志复位掉，
从而再次禁止发送保持寄存器空引发中断请求。此次“循环” 发送操作也随之结束。
*/
// 串行数据发送输出
void rs_write(struct tty_struct * tty)
{
	cli();
	if (!EMPTY(tty->write_q))
		// 如果写队列不空，则首先从 Ox3f9(或 0x2f9）读取中断允许寄存器内容，
		// 添上发送保持寄存器中断允许标志（位1）后，再写回该寄存器。
		// 这样，当发送保持寄存器空时 UART 就能够因期望获得欲发送的字符而引发中断。
		outb(inb_p(tty->write_q.data+1)|0x02,tty->write_q.data+1);
	sti();
}
